/*
  nvs_buffer.c - RAM based non-volatile storage buffer/emulation

  Part of grblHAL

  Copyright (c) 2017-2024 Terje Io
  Copyright (c) 2012-2016 Sungeun K. Jeon for Gnea Research LLC
  Copyright (c) 2009-2011 Simen Svale Skogsrud

  Grbl is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  Grbl is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License.
  along with Grbl.  If not, see <http://www.gnu.org/licenses/>.
*/

//
// Can be used by MCUs with no nonvolatile storage options, be sure to allocate enough heap memory before use
//

#include <string.h>
#include <stdlib.h>
#include <assert.h>

#include "hal.h"
#include "nvs_buffer.h"
#include "protocol.h"
#include "settings.h"
#include "gcode.h"
#include "nvs.h"

static uint8_t *nvsbuffer = NULL;
static nvs_io_t physical_nvs;
static bool dirty;
uint32_t nvs_size_max = NVS_SIZE;
settings_dirty_t settings_dirty;

typedef struct {
    uint16_t addr;
    uint8_t type;
    uint8_t offset;
} emap_t;

#define NVS_GROUP_GLOBAL 0
#define NVS_GROUP_TOOLS 1
#define NVS_GROUP_PARAMETERS 2
#define NVS_GROUP_STARTUP 3
#define NVS_GROUP_BUILD 4

// TODO : (sizeof(coord_data_t)为什么要选取这个数据类型的长度作为分段的依据不是很明确
#define PARAMETER_ADDR(n) (NVS_ADDR_PARAMETERS + n * (sizeof(coord_data_t) + NVS_CRC_BYTES))
#define STARTLINE_ADDR(n) (NVS_ADDR_STARTUP_BLOCK + n * (sizeof(stored_line_t) + NVS_CRC_BYTES))
#if N_TOOLS // 跟刀具有关,默认没有刀库
#define TOOL_ADDR(n) (NVS_ADDR_TOOL_TABLE + n * (sizeof(tool_data_t) + NVS_CRC_BYTES))
#endif
// 这个结构体将NVS区分成了很多扇区
static const emap_t target[] = {
    {NVS_ADDR_GLOBAL, NVS_GROUP_GLOBAL, 0},         // 这个是全局参数区

    {PARAMETER_ADDR(0), NVS_GROUP_PARAMETERS, 0},   //---------start-----------
    {PARAMETER_ADDR(1), NVS_GROUP_PARAMETERS, 1},
    {PARAMETER_ADDR(2), NVS_GROUP_PARAMETERS, 2},
    {PARAMETER_ADDR(3), NVS_GROUP_PARAMETERS, 3},
    {PARAMETER_ADDR(4), NVS_GROUP_PARAMETERS, 4},
    {PARAMETER_ADDR(5), NVS_GROUP_PARAMETERS, 5},   //这部分是参数分区
    {PARAMETER_ADDR(6), NVS_GROUP_PARAMETERS, 6},
    {PARAMETER_ADDR(7), NVS_GROUP_PARAMETERS, 7},
    {PARAMETER_ADDR(8), NVS_GROUP_PARAMETERS, 8},
    {PARAMETER_ADDR(9), NVS_GROUP_PARAMETERS, 9},
    {PARAMETER_ADDR(10), NVS_GROUP_PARAMETERS, 10},
    {PARAMETER_ADDR(11), NVS_GROUP_PARAMETERS, 11}, //----------end----------
    {STARTLINE_ADDR(0), NVS_GROUP_STARTUP, 0},      //---------start 默认参数分区-----------
    {STARTLINE_ADDR(1), NVS_GROUP_STARTUP, 1},      //-------------end------------------
#if N_STARTUP_LINE > 2
#error Increase number of startup line entries!
#endif
    {NVS_ADDR_BUILD_INFO, NVS_GROUP_BUILD, 0},
#if N_TOOLS
    {TOOL_ADDR(0), NVS_GROUP_TOOLS, 0},
    {TOOL_ADDR(1), NVS_GROUP_TOOLS, 1},
    {TOOL_ADDR(2), NVS_GROUP_TOOLS, 2},
    {TOOL_ADDR(3), NVS_GROUP_TOOLS, 3},
    {TOOL_ADDR(4), NVS_GROUP_TOOLS, 4},
    {TOOL_ADDR(5), NVS_GROUP_TOOLS, 5},
    {TOOL_ADDR(6), NVS_GROUP_TOOLS, 6},
    {TOOL_ADDR(7), NVS_GROUP_TOOLS, 7},
#if N_TOOLS > 8
    {TOOL_ADDR(8), NVS_GROUP_TOOLS,  8},
    {TOOL_ADDR(9), NVS_GROUP_TOOLS,  9},
    {TOOL_ADDR(10), NVS_GROUP_TOOLS, 10},
    {TOOL_ADDR(11), NVS_GROUP_TOOLS, 11},
    {TOOL_ADDR(12), NVS_GROUP_TOOLS, 12},
    {TOOL_ADDR(13), NVS_GROUP_TOOLS, 13},
    {TOOL_ADDR(14), NVS_GROUP_TOOLS, 14},
    {TOOL_ADDR(15), NVS_GROUP_TOOLS, 15},
#endif
#if N_TOOLS > 16
    {TOOL_ADDR(16), NVS_GROUP_TOOLS, 16},
    {TOOL_ADDR(17), NVS_GROUP_TOOLS, 17},
    {TOOL_ADDR(18), NVS_GROUP_TOOLS, 18},
    {TOOL_ADDR(19), NVS_GROUP_TOOLS, 19},
    {TOOL_ADDR(20), NVS_GROUP_TOOLS, 20},
    {TOOL_ADDR(21), NVS_GROUP_TOOLS, 21},
    {TOOL_ADDR(22), NVS_GROUP_TOOLS, 22},
    {TOOL_ADDR(23), NVS_GROUP_TOOLS, 23},
    {TOOL_ADDR(24), NVS_GROUP_TOOLS, 24},
    {TOOL_ADDR(25), NVS_GROUP_TOOLS, 25},
    {TOOL_ADDR(26), NVS_GROUP_TOOLS, 26},
    {TOOL_ADDR(27), NVS_GROUP_TOOLS, 27},
    {TOOL_ADDR(28), NVS_GROUP_TOOLS, 28},
    {TOOL_ADDR(29), NVS_GROUP_TOOLS, 29},
    {TOOL_ADDR(30), NVS_GROUP_TOOLS, 30},
    {TOOL_ADDR(31), NVS_GROUP_TOOLS, 31},
#endif
#endif
    {0, 0, 0} // list termination - do not remove
};

inline static uint8_t ram_get_byte (uint32_t addr)
{
    return nvsbuffer[addr];
}

inline static void ram_put_byte (uint32_t addr, uint8_t new_value)
{
    //TODO : settings_dirty.version 和 dirty两个变量的作用还不明确
    // settings_dirty.version 通常用于指示设置的版本是否已更改。每当设置被修改时，settings_dirty.version 会被标记为 true，以表示设置已被更新。
    // version 变量可能还涉及到设置格式的升级，特别是当新的固件版本具有不同的配置格式时。标记为true有助于固件识别该设置是否已更新，并在必要时进行迁移或转换。
    
    // dirty 变量标志某个特定的设置或配置是否已更改，需要进行存储或保存。这里的 dirty 一般作为一个临时标志，用于表示设置的数据是否与存储中持久化的版本不同。
    // 比如$100 = 1这样一个操作之后,dirty = 1
    if(addr == 0)
        settings_dirty.version = true;
    dirty = dirty || nvsbuffer[addr] != new_value || addr == 0;
    nvsbuffer[addr] = new_value;
}

static nvs_transfer_result_t memcpy_to_ram (uint32_t destination, uint8_t *source, uint32_t size, bool with_checksum)
{
    // hal.nvs.driver_area.address : 默认为0,在 nvs_alloc 函数中会操作这个参数
    // nvs_alloc函数的调用跟运动学有关
    // NOTE : 暂时跳过,后面分析的时候会知道这个地方操作的时候GRBL_NVS_END之后的空间
    if(hal.nvs.driver_area.address && destination > hal.nvs.driver_area.address + hal.nvs.driver_area.size)
        return physical_nvs.memcpy_to_nvs(destination, source, size, with_checksum);

    uint32_t dest = destination;
    // with_checksum - 这个形参表示是否启用校验
    uint8_t checksum = with_checksum ? calc_checksum(source, size) : 0;

    dirty = false;

    for(; size > 0; size--)//在这里写内存
        ram_put_byte(dest++, *(source++));

    if(with_checksum)
        ram_put_byte(dest, checksum);// 在所有的参数后面加了一个校验

    if(settings_dirty.version || source == hal.nvs.driver_area.mem_address)
        dirty = true;
    
    if(dirty && physical_nvs.type != NVS_None) {

        uint8_t idx = 0;

        settings_dirty.is_dirty = true;

        if(hal.nvs.driver_area.address && destination >= hal.nvs.driver_area.address)
            settings_dirty.driver_settings = true;

        else {
            // target[idx].addr数组的详细信息见定义处
            do {
                if(target[idx].addr == destination){
                    break;
                }
            // 这是在遍历整个NVS区,将destination指向的NVS地址提取出来
            } while(target[++idx].addr);

            // 处理target[idx].addr位置存储数据的数据类型
            // settings_dirty.coord_data这些标志位是为了记录对应的区域有没有被更改
            // 比如说操作了 {PARAMETER_ADDR(0), NVS_GROUP_PARAMETERS, 0}, 这个扇区, 那settings_dirty.coord_data
            // 的bit 0就会设置为1 标记该处被更改了
            if(target[idx].addr) switch(target[idx].type) {

                case NVS_GROUP_GLOBAL:
                    settings_dirty.global_settings = true;
                    break;
#if N_TOOLS
                case NVS_GROUP_TOOLS:
                    settings_dirty.tool_data |= (1 << target[idx].offset);
                    break;
#endif
                case NVS_GROUP_PARAMETERS:
                    settings_dirty.coord_data |= (1 << target[idx].offset);
                    break;

                case NVS_GROUP_STARTUP:
                    settings_dirty.startup_lines |= (1 << target[idx].offset);
                    break;

                case NVS_GROUP_BUILD:
                    settings_dirty.build_info = true;
                    break;
            }
        }
    }
    return NVS_TransferResult_OK;
}

// 用于操作NVS的内存镜像
static nvs_transfer_result_t memcpy_from_ram (uint8_t *destination, uint32_t source, uint32_t size, bool with_checksum)
{
    if(hal.nvs.driver_area.address && source > hal.nvs.driver_area.address + hal.nvs.driver_area.size)
        return physical_nvs.memcpy_from_nvs(destination, source, size, with_checksum);

    uint8_t checksum = with_checksum ? calc_checksum(&nvsbuffer[source], size) : 0;

    // 读取 source 长度的数据到 *destination
    for(; size > 0; size--)
        *(destination++) = ram_get_byte(source++);

    return with_checksum ? (checksum == ram_get_byte(source) ? NVS_TransferResult_OK : NVS_TransferResult_Failed) : NVS_TransferResult_OK;
}

// Try to allocate RAM from heap for buffer/emulation.
bool nvs_buffer_alloc (void)
{
	static uint32_t nvs_size = NVS_SIZE;
    /*
        在int grbl_enter (void)中 hal结构体被清零了; hal.nvs.size_max = 0; 
    */
	if(hal.nvs.size_max > nvs_size) {
		nvs_size_max = min(4096, hal.nvs.size_max); // Limit to 4K for now
		if(nvsbuffer)
			free(nvsbuffer);
	}

	assert(nvs_size_max >= GRBL_NVS_SIZE);
    /*
        nvs_size_max 指定要申请的NVS块大小, 最大不超过 4096
    */
    if((nvsbuffer = malloc(nvs_size_max))) {
    	nvs_size = nvs_size_max;
        memset(nvsbuffer, 0xFF, nvs_size_max);
    }
    return nvsbuffer != NULL;
}

void nvs_buffer_free (void)
{
    if(nvsbuffer) {
        nvs_buffer_sync_physical();
        free(nvsbuffer);
    }
}
// Switch over to RAM based copy.
// Changes to RAM based copy will be written to physical storage when grblHAL is in IDLE state.
bool nvs_buffer_init (void)
{

    // 在grbl_enter中 hal.nvs.size = GRBL_NVS_SIZE;
    // 这个操作是为了字节对齐
    // 举例 : 67 - 1 = 66 | 0x03 = 67 + 1 = 68; 68/4 = 16对齐完成
    hal.nvs.size = ((hal.nvs.size - 1) | 0x03) + 1; // Ensure NVS area ends on a word boundary

    if(nvsbuffer) { // 这个条件在nvs_buffer_alloc中已经成立

        // hal.nvs里面中指定了NVS存储器类型，物理存储器的读写接口,复制到 physical_nvs中
        memcpy(&physical_nvs, &hal.nvs, sizeof(nvs_io_t)); // save pointers to physical storage handler functions

        // Copy physical storage content to RAM when available
        // 这一段的作用是将物理NVS中存储的配置文件(参数)复制到RAM中
        if(physical_nvs.type == NVS_Flash)
            physical_nvs.memcpy_from_flash(nvsbuffer);
        else if(physical_nvs.type != NVS_None)
            physical_nvs.memcpy_from_nvs(nvsbuffer, 0, GRBL_NVS_SIZE + hal.nvs.driver_area.size, false);

        // Switch hal to use RAM version of non-volatile storage data
        // 这里提供了操作RAM中数据的接口
        hal.nvs.type = NVS_Emulated;
        hal.nvs.get_byte = &ram_get_byte;
        hal.nvs.put_byte = &ram_put_byte;
        hal.nvs.memcpy_to_nvs = &memcpy_to_ram;
        hal.nvs.memcpy_from_nvs = &memcpy_from_ram;
        hal.nvs.memcpy_from_flash = NULL;
        hal.nvs.memcpy_to_flash = NULL;
        // If no physical storage available or if NVS import fails copy default settings to RAM
        // and write out to physical storage when available.
        // NVS区还没有初始化过或者默认参数有变更
        if(physical_nvs.type == NVS_None || ram_get_byte(0) != SETTINGS_VERSION) {
            settings_restore(settings_all); // TODO : 将默认参数写入到NVS区，但是这个功能不是NVS提供的,略过。
            // 根据存储介质调用对应的物理接口将RAM中的数据搬运到FLASH中
            if(physical_nvs.type == NVS_Flash)
                physical_nvs.memcpy_to_flash(nvsbuffer);
            else if(physical_nvs.memcpy_to_nvs)
                physical_nvs.memcpy_to_nvs(0, nvsbuffer, GRBL_NVS_SIZE + hal.nvs.driver_area.size, false);
            if(physical_nvs.type != NVS_None)
                grbl.report.status_message(Status_SettingReadFail);
        }
    } else
        protocol_enqueue_foreground_task(report_warning, "Not enough heap for NVS buffer!"); // TODO : 这个接口有protocol提供,先略过

    // Clear settings dirty flags
    memset(&settings_dirty, 0, sizeof(settings_dirty_t));

    return nvsbuffer != NULL;
}

// Allocate NVS block for driver settings.
// NOTE: allocation has to be done before content is copied from physical storage.
// 这个函数跟运动学有关,操作在NVS存储块中偏移GRBL_NVS_SIZE字节后的地址
nvs_address_t nvs_alloc (size_t size)
{
    static uint8_t *mem_address;

    nvs_address_t addr = 0;

    // Check if already switched to emulation or buffer allocation failed, return NULL if so.
    if(hal.nvs.type == NVS_Emulated || nvsbuffer == NULL)
        return 0;

    if(hal.nvs.driver_area.address == 0) {
        hal.nvs.driver_area.address = GRBL_NVS_SIZE;
        // nvsbuffer是申请到的内存的首地址,nvsbuffer + GRBL_NVS_SIZE的意思是 hal.nvs.driver_area.mem_address指向的地址是nvsbuffer开始偏移GRBL_NVS_SIZE
        // 避开了存储配置参数，启动脚本的位置
        hal.nvs.driver_area.mem_address = mem_address = nvsbuffer + GRBL_NVS_SIZE;
    }

    size += NVS_CRC_BYTES; // add room for checksum.
    if(hal.nvs.driver_area.size + size < (nvs_size_max - GRBL_NVS_SIZE)) { // 检查有没有越界
        mem_address = (uint8_t *)((uint32_t)(mem_address - 1) | 0x03) + 1; // Align to word boundary
        addr = mem_address - nvsbuffer;//得到字节对齐之后的真正的起始地址
        mem_address += size;
        hal.nvs.driver_area.size = mem_address - hal.nvs.driver_area.mem_address;//末端地址-起始地址 = 实际消耗掉的内存长度
        hal.nvs.size = GRBL_NVS_SIZE + hal.nvs.driver_area.size + 1;            //更新NVS的实际大小
    }

    return addr;//在偏移GRBL_NVS_SIZE后并且经过了4字节对齐的起始地址
}

// Write RAM changes to physical storage
void nvs_buffer_sync_physical (void)
{
    if(!settings_dirty.is_dirty)        // 没有更改不做任何处理
        return;
    if(physical_nvs.memcpy_to_nvs) {    //检查有没有提供物理存储器的接口 
        if(settings_dirty.version)      // 参数的版本是否有变更,有的话把新版本号写入到NVS区
            settings_dirty.version = physical_nvs.memcpy_to_nvs(0, nvsbuffer, 1, false) != NVS_TransferResult_OK;

        if(settings_dirty.global_settings)  // 检查是不是有全局参数($值)更新,有的话写入到NVS区,写入成功后会清理掉变更标志
            settings_dirty.global_settings = physical_nvs.memcpy_to_nvs(NVS_ADDR_GLOBAL, (uint8_t *)(nvsbuffer + NVS_ADDR_GLOBAL), sizeof(settings_t) + NVS_CRC_BYTES, false) != NVS_TransferResult_OK;
        if(settings_dirty.build_info)       // 检查是不是有编译信息($I能查询到的那些参数)更新,有的话写入到NVS区
            settings_dirty.build_info = physical_nvs.memcpy_to_nvs(NVS_ADDR_BUILD_INFO, (uint8_t *)(nvsbuffer + NVS_ADDR_BUILD_INFO), sizeof(stored_line_t) + NVS_CRC_BYTES, false) != NVS_TransferResult_OK;

        uint_fast8_t idx = N_STARTUP_LINE, offset;
        if(settings_dirty.startup_lines) do {// 检查启动脚本有没有修改
            idx--;
            if(bit_istrue(settings_dirty.startup_lines, bit(idx))) {// 检索出哪一个段被修改了，然后将对应的变更写入到非易失性存储中
                bit_false(settings_dirty.startup_lines, bit(idx));  // 写入成功后会把对应的变更标志清零
                                                                    // 下面这些do{}while循环都是这个思想
                offset = NVS_ADDR_STARTUP_BLOCK + idx * (sizeof(stored_line_t) + NVS_CRC_BYTES);
                if(physical_nvs.memcpy_to_nvs(offset, (uint8_t *)(nvsbuffer + offset), sizeof(stored_line_t) + NVS_CRC_BYTES, false) == NVS_TransferResult_OK)
                    bit_false(settings_dirty.startup_lines, bit(idx));
            }
        } while(idx);

        idx = N_CoordinateSystems;
        if(settings_dirty.coord_data) do {
            if(bit_istrue(settings_dirty.coord_data, bit(idx))) {
                offset = NVS_ADDR_PARAMETERS + idx * (sizeof(coord_data_t) + NVS_CRC_BYTES);
                if(physical_nvs.memcpy_to_nvs(offset, (uint8_t *)(nvsbuffer + offset), sizeof(coord_data_t) + NVS_CRC_BYTES, false) == NVS_TransferResult_OK)
                    bit_false(settings_dirty.coord_data, bit(idx));
            }
        } while(idx--);

        if(settings_dirty.driver_settings) {
            if(hal.nvs.driver_area.size > 0)
                settings_dirty.driver_settings = physical_nvs.memcpy_to_nvs(hal.nvs.driver_area.address, (uint8_t *)(nvsbuffer + hal.nvs.driver_area.address), hal.nvs.driver_area.size, false) != NVS_TransferResult_OK;
            else
                settings_dirty.driver_settings = false;
        }

#if N_TOOLS
        idx = N_TOOLS;
        if(settings_dirty.tool_data) do {
            idx--;
            if(bit_istrue(settings_dirty.tool_data, bit(idx))) {
                offset = NVS_ADDR_TOOL_TABLE + idx * (sizeof(tool_data_t) + NVS_CRC_BYTES);
                if(physical_nvs.memcpy_to_nvs(offset, (uint8_t *)(nvsbuffer + offset), sizeof(tool_data_t) + NVS_CRC_BYTES, false) == NVS_TransferResult_OK)
                    bit_false(settings_dirty.tool_data, bit(idx));
            }
        } while(idx);
#endif
        // 如果上面的变更都成功写入了,那settings_dirty.is_dirty应该为0
        settings_dirty.is_dirty = settings_dirty.coord_data ||
                                   settings_dirty.global_settings ||
                                    settings_dirty.driver_settings ||
                                     settings_dirty.startup_lines ||
#if N_TOOLS
                                      settings_dirty.tool_data ||
#endif
                                       settings_dirty.build_info;

    } else if(physical_nvs.memcpy_to_flash) {
        // 如果非易失性存储是FLASH，那可以直接整体覆盖,这是存储器特性决定的
        uint_fast8_t retries = 4;
        do {
            if(physical_nvs.memcpy_to_flash(nvsbuffer))
                retries = 0;
            else if(--retries == 0)
                report_message("Settings write failed!", Message_Warning);
        } while(retries);
        memset(&settings_dirty, 0, sizeof(settings_dirty_t));
    }
}

nvs_io_t *nvs_buffer_get_physical (void)
{
    return hal.nvs.type == NVS_Emulated ? &physical_nvs : &hal.nvs;
}

#ifdef DEBUGOUT

#include "report.h"

void nvs_memmap (void)
{
    char buf[30];

    report_message("NVS Area: addr size", Message_Plain);

    strcpy(buf, "Global: ");
    strcat(buf, uitoa(NVS_ADDR_GLOBAL));
    strcat(buf, " ");
    strcat(buf, uitoa(sizeof(settings_t) + NVS_CRC_BYTES));
    report_message(buf, Message_Plain);

    strcpy(buf, "Parameters: ");
    strcat(buf, uitoa(NVS_ADDR_PARAMETERS));
    strcat(buf, " ");
    strcat(buf, uitoa(N_CoordinateSystems * (sizeof(coord_data_t) + NVS_CRC_BYTES)));
    report_message(buf, Message_Plain);

    strcpy(buf, "Startup block: ");
    strcat(buf, uitoa(NVS_ADDR_STARTUP_BLOCK));
    strcat(buf, " ");
    strcat(buf, uitoa(N_STARTUP_LINE * (sizeof(stored_line_t) + NVS_CRC_BYTES)));
    report_message(buf, Message_Plain);

    strcpy(buf, "Build info: ");
    strcat(buf, uitoa(NVS_ADDR_BUILD_INFO));
    strcat(buf, " ");
    strcat(buf, uitoa(sizeof(stored_line_t) + NVS_CRC_BYTES));
    report_message(buf, Message_Plain);

#if N_TOOLS
    strcpy(buf, "Tool table: ");
    strcat(buf, uitoa(NVS_ADDR_TOOL_TABLE));
    strcat(buf, " ");
    strcat(buf, uitoa(N_TOOLS * (sizeof(tool_data_t) + NVS_CRC_BYTES)));
    report_message(buf, Message_Plain);
#endif

    strcpy(buf, "Driver: ");
    strcat(buf, uitoa(hal.nvs.driver_area.address));
    strcat(buf, " ");
    strcat(buf, uitoa(hal.nvs.driver_area.size));
    report_message(buf, Message_Plain);
}

#endif
